package dk.viauc.cmclang.intlang;
import java.io.*;
import dk.viauc.cmclang.intlang.ast.*;
import TAM.*;
public class Encoder implements Visitor{

	private int nextAdr = Machine.CB;
	private int currentLevel = 0;
	
	private void emit( int op, int n, int r, int d )
	{
		System.out.println("Encoder.emit() "+op);
		if( n > 255 ) {
			System.out.println( "Operand too long" );
			n = 255;
		}
		
		Instruction instr = new Instruction();
		instr.op = op;
		instr.n = n;
		instr.r = r;
		instr.d = d;
		
		if( nextAdr >= Machine.PB )
			System.out.println( "Program too large" );
		else
			Machine.code[nextAdr++] = instr;
	}
	
	private void patch( int adr, int d)
	{
		Machine.code[adr].d = d;
	}
	
	private int displayRegister( int currentLevel, int entityLevel)
	{
		if( entityLevel == 0 )
			return Machine.SBr;
		else if( currentLevel - entityLevel <= 6 )
			return Machine.LBr + currentLevel - entityLevel;
		else {
			System.out.println( "Accessing across to many levels" );
			return Machine.L6r;
		}
	}
	
	public void saveTargetProgram( String fileName )
	{
		System.out.println("Encoder.saveTargetProgram()");
		try {
			DataOutputStream out = new DataOutputStream( new FileOutputStream( fileName ) );
			
			for( int i = Machine.CB; i < nextAdr; ++i )
				Machine.code[i].write( out );
			                      
			out.close();
		} catch( Exception ex ) {
			ex.printStackTrace();
			System.out.println( "Trouble writing " + fileName );
		}
	}
	
	
	public void encode( Program p )
	{
		p.visit( this, null );
	}
	
	public Object visitProgram(Program program, Object arg) {
		currentLevel = 0;
		program.block.visit(this, new Address());
		emit(Machine.HALTop, 0, 0, 0);
		return null;
	}


	public Object visitBlock(Block block, Object arg) {
		
		for(FunctionDeclaration dec: block.functions){
			dec.visit(this, arg);
		}
		return null;
	}

	public Object visitFunctionDeclaration(
			FunctionDeclaration functionDeclaration, Object arg) {
		functionDeclaration.address = new Address(currentLevel, nextAdr);
		System.out.println("Encoder.visitFunctionDeclaration()"+functionDeclaration.ident.spelling);
		int size = 0;
		Address adr = new Address((Address)arg);
		functionDeclaration.type.visit(this,null);
		
		if(functionDeclaration.params != null)
		{
			size = ((Integer)functionDeclaration.params.visit(this, adr)).intValue();
			functionDeclaration.params.visit(this,  new Address(adr, -size));
		}
		
		functionDeclaration.statements.visit(this, arg);
		if(!functionDeclaration.ident.spelling.equals("#MAIN"))
		{	
		//	emit(Machine.RETURNop,1,0,size);
		}
		
		
		return arg;
	}

	public Object visitIdentifier(Identifier identifier, Object arg) {
		return null;
	}

	public Object visitType(Type type, Object arg) {
		return type.type;
	}


	public Object visitDeclarationList(DeclarationList declarationList,
			Object arg) {
		int startDisplacement = ((Address)arg).displacement;
		
		for(Declaration dec : declarationList.dec) {
			arg = dec.visit(this, arg);
		}
		
		Address adr = (Address) arg;
		int size = adr.displacement - startDisplacement;
		
		return new Integer(size);
	}

	public Object visitStatementList(StatementList statementList, Object arg) {
		for(Statement stat : statementList.statements)
			stat.visit(this, arg);
		return null;
	}

	public Object visitVariableDeclaration(
			VariableDeclaration variableDeclaration, Object arg) {
		variableDeclaration.adr = (Address)arg;
		
		int size = 1; //=1 if boolean
		if(variableDeclaration.type.type.equals("INT"))
			size=4;
		
		emit(Machine.PUSHop,0,0,size);
		
		if(variableDeclaration.term != null)
			variableDeclaration.term.visit(this, new Boolean(true));
		return new Address((Address)arg,1);
	}

	public Object visitArrayGetExpression(
			ArrayGetExpression arrayGetExpression, Object arg) {
		arrayGetExpression.id.visit(this, null);
		arrayGetExpression.il.visit(this, null);
		return null;
	}

	public Object visitBinaryExpression(BinaryExpression binaryExpression,
			Object arg) {
		boolean valueNeeded = ((Boolean)arg).booleanValue();
		String op = (String)binaryExpression.operator.visit(this, null);
		
		if(op.equals("BE")) {
			Address adr = (Address)binaryExpression.operand1.visit(this, new Boolean(false));
			binaryExpression.operand2.visit(this, new Boolean(true));
			
			int register = displayRegister(currentLevel, adr.level);
			emit(Machine.STOREop,1,register, adr.displacement);
			
			if(valueNeeded)
				emit(Machine.LOADop,1,register, adr.displacement);
		} else {
			binaryExpression.operand1.visit(this, arg);
			binaryExpression.operand2.visit(this, arg);
			
			if(valueNeeded)
				if(op.equals("PLUS"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.addDisplacement);
				else if (op.equals("MINUS"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.subDisplacement);
				else if(op.equals("MUL"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.multDisplacement);
				else if(op.equals("DIV"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.divDisplacement);
				else if(op.equals("MOD"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.modDisplacement);
				else if(op.equals("EQUAL"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.eqDisplacement);
				else if(op.equals("LESSEQ"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.leDisplacement);
				else if(op.equals("MOREEQ"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.geDisplacement);
				else if(op.equals("MORE"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.gtDisplacement);
				else if(op.equals("LESS"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.ltDisplacement);
				else if(op.equals("AND"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.andDisplacement);
				else if(op.equals("OR"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.orDisplacement);
				else if(op.equals("NOT"))
					emit(Machine.CALLop,0,Machine.PBr, Machine.notDisplacement);
		}
		return null;
	}

	public Object visitBoolLiteral(BoolLiteral boolLiteral, Object arg) {
		return new Boolean(boolLiteral.spelling);
	}

	public Object visitBoolLitExpression(BoolLitExpression boolLitExpression,
			Object arg) {
		boolean valueNeeded =((Boolean)arg).booleanValue();
		
		Boolean lit = (Boolean) boolLitExpression.bool.visit(this, null);
		int val = 0; //0 if false 1 if true
		if(lit.booleanValue())
			val =1;
		if(valueNeeded){
			emit(Machine.LOADLop,1,0, val);
		}
		return null;
	}

	public Object visitDecsStatement(DecsStatement decsStatement, Object arg) {
		decsStatement.stlist.visit(this, arg);
		return null;
	}


	public Object visitCallExpression(CallExpression callExpression, Object arg) {
		boolean valueNeeded = ((Boolean)arg).booleanValue();
		callExpression.explist.visit(this, null);
		
		Address adr = callExpression.decl.address;
		int register = displayRegister(currentLevel, adr.level);
		
		emit(Machine.CALLop, register, Machine.CB, adr.displacement);
		
		if(!valueNeeded)
			emit(Machine.POPop, 0, 0, 1);
		
		return null;
	}

	public Object visitExpList(ExpList expList, Object arg) {
		for(Expression exp : expList.exp)
			exp.visit(this,  new Boolean(true));
		return null;
	}

	public Object visitExpressionStatement(
			ExpressionStatement expressionStatement, Object arg) {
		expressionStatement.exp.visit(this, new Boolean(false));
		return null;
	}

	public Object visitIfStatement(IfStatement ifStatement, Object arg) {
		ifStatement.exp.visit(this,  new Boolean(true));
		
		int jump1Adr = nextAdr;
		emit(Machine.JUMPIFop, 0, Machine.CBr, 0);
		
		ifStatement.doPart.visit(this, null);
		
		int jump2Adr = nextAdr;
		emit(Machine.JUMPop, 0, Machine.CBr, 0);
		
		patch(jump1Adr, nextAdr);
		
		if(ifStatement.elsePart != null)
			ifStatement.elsePart.visit(this, null);
		
		patch(jump2Adr,nextAdr);
		return null;
	}

	public Object visitIntegerLiteral(IntegerLiteral integerLiteral, Object arg) {
		return new Integer(integerLiteral.spelling);
	}

	public Object visitIntLiteralList(IntLiteralList intLiteralList, Object arg) {
		for(IntegerLiteral intLit: intLiteralList.intlist)
			intLit.visit(this, null);
		return null;
	}


	public Object visitIntLitExpression(IntLitExpression intLitExpression,
			Object arg) {
		boolean valueNeeded = ((Boolean)arg).booleanValue();
		Integer lit = (Integer) intLitExpression.literal.visit(this,null);
	
		if(valueNeeded)
			emit(Machine.LOADLop,1,0, lit.intValue());
		return null;
	}

	public Object visitOperator(Operator operator, Object arg) {
		return operator.spelling;
	}

	public Object visitReadStatement(ReadStatement readStatement, Object arg) {
		readStatement.exp.visit(this, new Boolean(true));
		emit( Machine.CALLop, 0, Machine.PBr, Machine.getintDisplacement);
		emit( Machine.CALLop, 0, Machine.PBr, Machine.geteolDisplacement);
		return null;
	}
	
	public Object visitReturnStatement(ReturnStatement returnStatement,
			Object arg) {

		returnStatement.retExpresssion.visit(this, new Boolean(true));
		//TODO change size
		emit(Machine.RETURNop,1,0,-4);
	
		return null;
	}

	public Object visitStatements(Statements statements, Object arg) {
		for(Statement st: statements.stat)
			st.visit(this, null);
		return null;
	}

	public Object visitVarExpression(VarExpression varExpression, Object arg) {
		boolean valueNeeded = ((Boolean) arg).booleanValue();
		
		Address adr = varExpression.decl.adr;
		int register = displayRegister( currentLevel, adr.level );
		
		if( valueNeeded )
			emit( Machine.LOADop, 1, register, adr.displacement );
			
		return adr;
	}

	public Object visitWhileStatement(WhileStatement whileStatement, Object arg) {
		int startAdr = nextAdr;
		whileStatement.exp.visit(this, new Boolean(true));
		
		int jumpAdr = nextAdr;
		emit(Machine.JUMPIFop, 0, Machine.CBr,0);
		
		whileStatement.stats.visit(this, null);
		
		emit(Machine.JUMPop, 0, Machine.CBr, startAdr);
		patch(jumpAdr, nextAdr);
		
		return null;
	}

	public Object visitWriteStatement(WriteStatament writeStatament, Object arg) {
		writeStatament.exp.visit(this, new Boolean(true));
		
		emit( Machine.CALLop, 0, Machine.PBr, Machine.putintDisplacement );
		emit( Machine.CALLop, 0, Machine.PBr, Machine.puteolDisplacement );
		return null;
	}
	
	
}
